﻿using System.Windows.Controls;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Collections;
using DeepEarth.Core;
using System;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Linq;
using DeepEarth.Map.Core;
using DeepEarth.Core.Controls;
using DeepEarth.Map.Core.Utilities;
namespace DeepEarth.Toolkit.Geometry.OGC
{
    /// <summary>
    /// UI Element that takes a hierarchical collection of items, a template selector, and add child elements to a single canvas layer.
    /// Could be used to render objects with child collections that have the AtomicEntity attribute - currently not used by the Map Layers
    /// </summary>
    
    // This is a lot like a lightweight dynamic layer.
    public class CompositeElement : Canvas, IMultiLocation, ISelfPositioning
    {
        public TemplateSelector TemplateSelector { get; set; }

        private IEnumerable itemsSource;
        public IEnumerable ItemsSource
        {
            get
            {
                return itemsSource;
            }
            set
            {
                locations.Clear();

                if (itemsSource != null && itemsSource is INotifyCollectionChanged)
                {
                    INotifyCollectionChanged oldItems = itemsSource as INotifyCollectionChanged;
                    oldItems.CollectionChanged -= itemsSource_CollectionChanged;
                }

                itemsSource = value;

                suspendLayout = true;

                IEnumerable itemsSourceEnumerable = itemsSource as IEnumerable;
                int index = 0;
                foreach (var item in itemsSourceEnumerable)
                {
                    itemsSource_CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
                    index++;
                }

                suspendLayout = false;

                if (itemsSource != null && itemsSource is INotifyCollectionChanged)
                {
                    INotifyCollectionChanged newItems = itemsSource as INotifyCollectionChanged;
                    newItems.CollectionChanged -= itemsSource_CollectionChanged;
                }
            }
        }

        void itemsSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:

                    foreach (var item in e.NewItems)
                    {
                        ApplyTemplate(item);
                        IncludeInBounds(item);
                    }

                    break;
            }
        }

        void ApplyTemplate(object item)
        {
            DataTemplate t = this.TemplateSelector.FindTemplate<DataTemplate>(item);

            if (t == null && item is IEnumerable)
            {
                CompositeElement ae = new CompositeElement();
                ae.TemplateSelector = this.TemplateSelector;
                ae.ItemsSource = item as IEnumerable;
                this.Children.Add(ae);
            }

            if (item is IItemsContainer)
            {
                CompositeElement ae = new CompositeElement();
                ae.TemplateSelector = this.TemplateSelector;
                ae.ItemsSource = (item as IItemsContainer).Children;
                this.Children.Add(ae);
            }

            if (t != null)
            {
                FrameworkElement fe = MapHelper.CreateElement(t, item);
                if (fe != null)
                {
                    this.Children.Add(fe);
                }

                // if we're an IItemcontainer, then we've already dealt with the children
                if (t is HierarchicalDataTemplate && !(item is IItemsContainer))
                {
                    HierarchicalDataTemplate ht = t as HierarchicalDataTemplate;
                    if (ht != null)
                    {
                        if (ht.ItemsSource != null)
                        {
                            PropertyPath pp = ht.ItemsSource.Path;

                            Type itemType = item.GetType();
                            PropertyInfo pi = itemType.GetProperty(pp.Path);

                            IEnumerable enumerable = pi.GetValue(item, null) as IEnumerable;

                            if (enumerable != null)
                            {
                                CompositeElement ae = new CompositeElement();
                                ae.ItemsSource = enumerable;
                                ae.TemplateSelector = this.TemplateSelector;
                                this.Children.Add(ae);
                            }
                        }
                    }
                }
            }
        }

        private bool suspendLayout = false;

        void IncludeInBounds(object item)
        {
            if (item is IMultiLocation)
            {
                IMultiLocation multiLoc = item as IMultiLocation;

                foreach (Location l in multiLoc.Locations)
                {
                    locations.Add(l);
                }
            }
            else if ( item is ILocatable)
            {
                ILocatable locatable = item as ILocatable;
                locations.Add(locatable.Position);
            }
        }

        private ObservableCollection<Location> locations = new ObservableCollection<Location>();

        public IEnumerable<Location> Locations
        {
            get { return locations; }
        }

        // IMultiLocations can return no position unless a specific location is required
        public Location Position
        {
            get
            {
                return null;
            }
            set
            {
                ; // do nothing
            }
        }

        public void Refresh(Point offset, double maxBound)
        {
            if (suspendLayout)
            {
                return;
            }

            if (LocationToPoint == null)
            {
                CalculateLocationToPoint();
            }

            foreach (UIElement element in this.Children)
            {
                if (element is ISelfPositioning)
                {
                    ISelfPositioning sp = element as ISelfPositioning;
                    sp.LocationToPoint = this.LocationToPoint;
                    sp.Refresh(offset, maxBound);
                }

                var location = Layer.GetPosition(element);
                if (location != null)
                {
                    var p = LocationToPoint(location);

                    PositionOrigin origin = Layer.GetPositionOrigin(element);

                    double offsetX = 0;
                    double offsetY = 0;

                    FrameworkElement fe = element as FrameworkElement;
                    if (fe != null)
                    {
                        double width = fe.ActualWidth;
                        double height = fe.ActualHeight;
                        if (width == 0)
                        {
                            fe.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                            width = fe.DesiredSize.Width;
                            height = fe.DesiredSize.Height;
                        }

                        offsetX = width * origin.X;
                        offsetY = height * origin.Y;
                    }

                    SetLeft(element, p.X - offsetX);
                    SetTop(element, p.Y - offsetY);

                }
            }
        }

        public Func<Location, Point> LocationToPoint { get;set; }

        LocationRectangle BoundingBox { get; set; }

        private void CalculateLocationToPoint()
        {
            BoundingBox = new LocationRectangle(this.Locations);

            double aspectWidth = this.Width;
            double aspectHeight = this.Height;

            double aspectRatio = BoundingBox.Width / BoundingBox.Height;

            if (aspectRatio > 1)
            {
                //wide
                aspectHeight = this.Width / aspectRatio;

                if (aspectHeight > this.Height)
                {
                    aspectHeight = this.Height;

                    aspectWidth = aspectHeight * aspectRatio;
                }
            }
            else
            {
                //tall
                aspectWidth = this.Height * aspectRatio;

                if (aspectWidth > this.Width)
                {
                    aspectWidth = this.Width;
                    aspectHeight = aspectWidth / aspectRatio;
                }
            }

            this.LocationToPoint = (Location location) =>
                {
                    Point relativeLocation = new Point
                    {
                        X = Math.Abs(location.Longitude - BoundingBox.West),
                        Y = Math.Abs(location.Latitude - BoundingBox.North)
                    };

                    //Add pixels relative to Layer size.
                    Point pixel = new Point();

                    pixel.X = (relativeLocation.X / BoundingBox.Width) * aspectWidth;
                    pixel.Y = (relativeLocation.Y / BoundingBox.Height) * aspectHeight;

                    return pixel;
                };

        }
    }
}
